package de.hft.st.algodat.ctt.algo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import de.hft.st.algodat.ctt.model.ConstraintPeriod;
import de.hft.st.algodat.ctt.model.ConstraintRoom;
import de.hft.st.algodat.ctt.model.Course;
import de.hft.st.algodat.ctt.model.Curriculum;
import de.hft.st.algodat.ctt.model.Problem;
import de.hft.st.algodat.ctt.model.Solution;
import de.hft.st.algodat.ctt.model.Timeslot;

/**
 * myopic construction
 * 
 */

public class MyopicConstruction {
	private Problem pRef;
	private Solution sRef;
	private int[] cPri;
	private int[] tsPri;
	private EA eaRef;

	AllEvents allEvents;
	NonPrioritizedEvents nonPrioritizedEvents;
	NotAssignedEvents notAssignedEvents;
	AssignedEvents assignedEvents;
	OpenEvents openEvents;

	public MyopicConstruction(Problem theProblem, Solution theSolution, EA ea) {
		this.pRef = theProblem;
		this.sRef = theSolution;
		this.eaRef = ea;
		this.cPri = eaRef.getCPri();
		this.tsPri = eaRef.getTSPri();
	}

	public void constructFeasibleSolution() {
		// e - set of all events
		// eU - set of unassigned

		initConstruction();
		openEvents.setEvents(allEvents.getEvents());

		while (openEvents.getEvents().size() > 0) {
			// System.out.println("remaining opens: " +
			// openEvents.getEvents().size());
			// System.out.println("OPEN " + openEvents.getEvents().size());
			Event mostCritical = openEvents.getMostCriticalEvent();
			List<Timeslot> workerSlots = mostCritical.getAvailableTimeslots();
			if (workerSlots.size() > 0) {
				Timeslot dts = getIndexOfHighestPrioTS(workerSlots);
				dts.setAssigned(mostCritical.getCourse());
				// System.out.println("assigned to " + dts.getTsID());
				// this.eaRef.decreaseTSPri(dts.getTsID(), 1);
				assignedEvents.addEvent(mostCritical);
				openEvents.removeEvent(mostCritical);
				// this.eaRef.decreaseCoursePri(mostCritical.getCPriID(), 1);

				List<String> cDone = new ArrayList<String>();
				cDone.add(mostCritical.getCourse().getCID());

				List<Event> containerEvents = new ArrayList<Event>();
				for (Iterator<Event> eItr = openEvents.getEvents().iterator(); eItr
						.hasNext();) {
					Event eWorker = eItr.next();
					if (eWorker.getCourse().getId().equals(
							mostCritical.getCourse().getId())) {
						if (!cDone.contains(eWorker.getCourse().getCID())) {
							cDone.add(eWorker.getCourse().getCID());
							containerEvents.add(eWorker);
						}
					}
				}

				openEvents.setEvents(removeEvents(openEvents.getEvents(),
						containerEvents));
				// System.out.println("found " + containerEvents.size() +
				// " container courses");
				assignedEvents.addEvents(containerEvents);
			} else {
				notAssignedEvents.addEvent(mostCritical);
				this.eaRef.increaseCoursePri(mostCritical.getCPriID(),
						openEvents.getEvents().size());
				// this.eaRef.increaseCoursePri(mostCritical.getCPriID(), 10);
				openEvents.removeEvent(mostCritical);
				// System.out.println("cannot add " +
				// mostCritical.getCourse().getId());
				break;
			}
		}

		// System.out.println(assignedEvents.getEvents().size());
		int remaining = allEvents.getEvents().size()
				- assignedEvents.getEvents().size();
		System.out.println("assigned: " + assignedEvents.getEvents().size());
		System.out.println("all: " + allEvents.getEvents().size());
		if (remaining < 1000) {
			System.out.println(remaining);
		}
	}

	private Timeslot getIndexOfHighestPrioTS(List<Timeslot> workerSlots) {
		int highVal = -99999;
		Timeslot ts = null;
		for (Iterator<Timeslot> itr = workerSlots.iterator(); itr.hasNext();) {
			Timeslot worker = itr.next();
			if (tsPri[worker.getTsID()] > highVal) {
				highVal = tsPri[worker.getTsID()];
				ts = worker;
			}
		}
		return ts;
	}

	private void initConstruction() {
		allEvents = new AllEvents();
		// System.out.println("init" + allEvents.getEvents().size());
		openEvents = new OpenEvents();
		notAssignedEvents = new NotAssignedEvents();
		assignedEvents = new AssignedEvents();
	}

	private List<Event> removeEvents(List<Event> from, List<Event> removes) {
		List<Event> resultList = new ArrayList<Event>();
		for (Iterator<Event> itr = from.iterator(); itr.hasNext();) {
			Event worker = itr.next();
			if (!removes.contains(worker)) {
				resultList.add(worker);
			}
		}
		return resultList;
	}

	private class OpenEvents extends Events {
		protected Event getMostCriticalEvent() {
			int highVal = -1;
			Event mCrit = null;
			for (Iterator<Event> itr = this.events.iterator(); itr.hasNext();) {
				Event worker = itr.next();
				if (cPri[worker.getCPriID()] > highVal) {
					highVal = cPri[worker.getCPriID()];
					mCrit = worker;
				}
			}
			return mCrit;
		}

		protected Event getMostCriticalEvent_() {
			int lowVal = 999;
			Event mCrit = null;
			for (Iterator<Event> itr = this.events.iterator(); itr.hasNext();) {
				Event worker = itr.next();
				int numVal = worker.getAvailableTimeslots().size();
				if (numVal < lowVal) {
					lowVal = numVal;
					mCrit = worker;
				}
			}
			return mCrit;
		}
	}

	private class NotAssignedEvents extends Events {
	}

	private class NonPrioritizedEvents extends Events {
		protected Event getMostCriticalEvent() {
			int highVal = -1;
			Event mCrit = null;
			for (Iterator<Event> itr = this.events.iterator(); itr.hasNext();) {
				Event worker = itr.next();
				if (cPri[worker.getCPriID()] > highVal) {
					highVal = cPri[worker.getCPriID()];
					mCrit = worker;
				}
			}
			return mCrit;
		}
	}

	private class AllEvents extends Events {
		private AllEvents() {
			int cPriID = 0;
			for (Iterator<Curriculum> cuItr = pRef.getCurricula().iterator(); cuItr
					.hasNext();) {
				for (Iterator<Course> coItr = cuItr.next().getCList()
						.iterator(); coItr.hasNext();) {
					events.add(new Event(coItr.next(), cPriID));
					cPriID++;
				}
			}
		}
	}

	private class AssignedEvents extends Events {
		private AssignedEvents() {
		}
	}

	protected class Event {
		private Course course;
		private int cPriID;

		private Event(Course course, int cPriID) {
			this.course = course;
			this.cPriID = cPriID;
		}

		private List<Timeslot> getAvailableTimeslots() {
			String currID = this.course.getCID();
			Curriculum curriculumWorker = pRef.getCurriculumByID(currID);
			List<Curriculum> currWithSameCourse = pRef
					.getCurriculaWithSameCourse(currID, this.course.getId());
			List<Timeslot> availableTS = new ArrayList<Timeslot>();
			for (Iterator<Timeslot> itr = sRef.getTimeSlots().iterator(); itr
					.hasNext();) {
				Timeslot ts = itr.next();
				if (!ts.isAssigned()) {
					if (validateTS(curriculumWorker, ts)) {
						boolean valid = true;
						for (Iterator<Curriculum> cItr = currWithSameCourse
								.iterator(); cItr.hasNext();) {
							Curriculum curr = cItr.next();
							if (!validateTS(curr, ts)) {
								valid = false;
								break;
							}
						}
						if (valid) {
							availableTS.add(ts);
						}
					}
					// System.out.println("currworker invalidated");
				}
			}
			return availableTS;
		}

		private boolean validateTS(Curriculum curriculumWorker, Timeslot ts) {
			boolean valid = true;
			List<Timeslot> workerList = sRef.getAssignedTimeSlotsByTimeslot(ts);
			// System.out.println(workerList.size());
			for (Iterator<Timeslot> dItr = workerList.iterator(); dItr
					.hasNext();) {
				Timeslot tsW = dItr.next();
				// System.out.println("Testing " + tsW.getTsID() +
				// tsW.getRoom().getId() + tsW.getAssigned());
				if (tsW.isAssigned()
						&& curriculumWorker.hasCourse(tsW.getAssigned())) {
					// System.out.println("same time other curr course");
					valid = false;
					break;
				}
				if (/* ts.isAssigned() && */
				tsW.isAssigned()
						&& tsW.getAssigned().getTeacher().equals(
								this.course.getTeacher())) {
					valid = false;
					break;
				}
				List<ConstraintPeriod> cPeriods = pRef
						.getConstraintsPeriodForCourse(this.course);
				// if (cPeriods.size() > 0) {
				// System.out.println("retrieved " + cPeriods.size()
				// + " constraints for " + this.course.getId());
				// }
				for (Iterator<ConstraintPeriod> cPItr = cPeriods.iterator(); cPItr
						.hasNext();) {
					ConstraintPeriod cPWorker = cPItr.next();
					// System.out.println(cPWorker);
					List<Timeslot> cPTsWorker = cPWorker
							.getForbiddenTimeslots();
					for (Iterator<Timeslot> cPTsItr = cPTsWorker.iterator(); cPTsItr
							.hasNext();) {
						Timeslot dummy = cPTsItr.next();
						// if(this.course.getId().equals("c0001")) {
						// System.out.println("c0001 detected");
						// System.out.println((dummy.getDay()-1) + " " +
						// ts.getDay() + " "
						// + (dummy.getPeriod()-1) + " " + ts.getPeriod());
						// }

						if (dummy.getDay() == (ts.getDay() - 1)
								&& dummy.getPeriod() == (ts.getPeriod() - 1)) {
							valid = false;
							break;
						}
					}
				}

				List<ConstraintRoom> cRooms = pRef.getConstraintsRoom();
				for (Iterator<ConstraintRoom> cRItr = cRooms.iterator(); cRItr
						.hasNext();) {
					ConstraintRoom cRWorker = cRItr.next();
					if (!this.course.getId().equals(cRWorker.getCourseID())) {
						continue;
					}
					List<String> cRTsWorker = cRWorker.getForbiddenRooms();
					for (Iterator<String> cRRItr = cRTsWorker.iterator(); cRRItr
							.hasNext();) {
						String dummy = cRRItr.next();
						if (ts.getRoom().getId().equals(dummy)) {
							valid = false;
							break;
						}
					}
				}
			}
			return valid;
		}

		private int getCPriID() {
			return this.cPriID;
		}

		private Course getCourse() {
			return this.course;
		}
	}

	private class Events {
		List<Event> events = new ArrayList<Event>();

		protected void addEvent(Event event) {
			this.events.add(event);
		}

		protected void addEvents(List<Event> events) {
			for (Iterator<Event> itr = events.iterator(); itr.hasNext();) {
				this.events.add(itr.next());
			}
		}

		protected void removeEvent(Event event) {
			this.events.remove(event);
		}

		protected void setEvents(List<Event> events) {
			this.events = events;
		}

		protected List<Event> getEvents() {
			return this.events;
		}

		protected void emptyEventsList() {
			this.events = new ArrayList<Event>();
		}
	}
}
